// <copyright file="YUIControlDescriptor.cs" company="YUIAsp.Net">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>raul popescu</author>
// <email>dev@yuiasp.net</email>
// <date>2009-01-30</date>
// <summary>YUIAsp.Net File</summary>

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Web.UI;

namespace YUIAspNet.Common
{
    internal class YUIControlDescriptor : ScriptControlDescriptor
    {
        string _clientID;
        Dictionary<string, string> _events = new Dictionary<string,string>();
        Dictionary<string, ClientProperty> _properties = new Dictionary<string, ClientProperty>();
        YUIJavascriptSerializer _serializer = new YUIJavascriptSerializer();

        public YUIControlDescriptor(string type, string clientID) : base(type, clientID) 
        {
            _clientID = clientID;
        }

        public void AddProperty(string name, ClientProperty val) 
        {
            this._properties.Add(name, val);
        }

        public new void AddEvent(string name, string eventName)
        {
            this._events.Add(name, eventName);
        }

        protected override string GetScript()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("$create(");
            builder.Append(this.Type);
            builder.Append(", ");
            this.AppendPropertiesScript(builder);
            builder.Append(", ");
            this.AppendEventsScript(builder);
            builder.Append(", null");
            //this.AppendReferencesScript(builder);
            if (this._clientID != null)
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, ", $get(\"{0}\")", QuoteString(this._clientID));
            }
            builder.Append(");");
            return builder.ToString();
        }

        private void AppendPropertiesScript(StringBuilder builder)
        {
            bool flag = true;
            if ((this._properties != null) && (this._properties.Count > 0))
            {
                foreach (KeyValuePair<string, ClientProperty> pair in this._properties)
                {                    
                    StringBuilder parsed;
                    if (pair.Value.Parsed)
                        parsed = pair.Value.Value as StringBuilder;
                    else
                        parsed = _serializer.YUISerialize(pair.Value.Value);

                    if (parsed != null)
                    {
                        if (flag)
                        {
                            builder.Append("{");
                            flag = false;
                        }
                        else
                        {
                            builder.Append(",");
                        }

                        builder.AppendFormat(CultureInfo.InvariantCulture, "\"{0}\":{1}", QuoteString(pair.Key), parsed);
                    }
                }
            }
            if (flag)
            {
                builder.Append("null");
            }
            else
            {
                builder.Append("}");
            }
        }

        private void AppendEventsScript(StringBuilder builder)
        {
            if ((this._events != null) && (this._events.Count > 0))
            {
                builder.Append('{');
                bool flag = true;
                foreach (KeyValuePair<string, string> pair in this._events)
                {
                    if (flag)
                    {
                        flag = false;
                    }
                    else
                    {
                        builder.Append(',');
                    }
                    builder.Append('"');
                    builder.Append(QuoteString(pair.Key));
                    builder.Append('"');
                    builder.Append(':');
                    builder.Append(pair.Value);
                }
                builder.Append("}");
            }
            else
            {
                builder.Append("null");
            }
        }

        #region Helper methods
        private static void AppendCharAsUnicode(StringBuilder builder, char c)
        {
            builder.Append(@"\u");
            builder.AppendFormat(CultureInfo.InvariantCulture, "{0:x4}", new object[] { (int)c });
        }

        internal static string QuoteString(string value)
        {
            StringBuilder builder = null;
            if (string.IsNullOrEmpty(value))
            {
                return string.Empty;
            }
            int startIndex = 0;
            int count = 0;
            for (int i = 0; i < value.Length; i++)
            {
                char c = value[i];
                if ((((c == '\r') || (c == '\t'))
                    || ((c == '"') || (c == '\'')))
                    || ((((c == '<') || (c == '>')) || ((c == '\\') || (c == '\n'))) || (((c == '\b') || (c == '\f')) || (c < ' '))))
                {
                    if (builder == null)
                    {
                        builder = new StringBuilder(value.Length + 5);
                    }
                    if (count > 0)
                    {
                        builder.Append(value, startIndex, count);
                    }
                    startIndex = i + 1;
                    count = 0;
                }
                switch (c)
                {
                    case '<':
                    case '>':
                    case '\'':
                        {
                            AppendCharAsUnicode(builder, c);
                            continue;
                        }
                    case '\\':
                        {
                            if (builder != null) builder.Append(@"\\");
                            continue;
                        }
                    case '\b':
                        {
                            if (builder != null) builder.Append(@"\b");
                            continue;
                        }
                    case '\t':
                        {
                            if (builder != null) builder.Append(@"\t");
                            continue;
                        }
                    case '\n':
                        {
                            if (builder != null) builder.Append(@"\n");
                            continue;
                        }
                    case '\f':
                        {
                            if (builder != null) builder.Append(@"\f");
                            continue;
                        }
                    case '\r':
                        {
                            if (builder != null) builder.Append(@"\r");
                            continue;
                        }
                    case '"':
                        {
                            if (builder != null) builder.Append("\\\"");
                            continue;
                        }
                }
                if (c < ' ')
                {
                    AppendCharAsUnicode(builder, c);
                }
                else
                {
                    count++;
                }
            }
            if (builder == null)
            {
                return value;
            }
            if (count > 0)
            {
                builder.Append(value, startIndex, count);
            }
            return builder.ToString();
        } 
        #endregion
    }

    /// <summary>
    /// Represents an object which values will be sent to the client.
    /// </summary>
    public class ClientProperty
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientProperty"/> class.
        /// </summary>
        /// <param name="value">The value.</param>
        public ClientProperty(object value)
            : this(value, false)
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientProperty"/> class.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="alreadySerialized">if set to <c>true</c> the property will be assumed to be already serialized. In this case the values needs to be a StringBuilder.</param>
        public ClientProperty(object value, bool alreadySerialized)
        {
            Parsed = alreadySerialized;
            if (alreadySerialized && value.GetType() != typeof(StringBuilder))
                throw new ArgumentException("Parsed value not of type StringBuilder: "+value);
            Value = value;
        }

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ClientProperty"/> is already serialized.
        /// In this case the values needs to be a StringBuilder.
        /// </summary>
        /// <value><c>true</c> if parsed; otherwise, <c>false</c>.</value>
        public bool Parsed { get; set; }
    }
}
